/*  

Cafechess Library: Cover basic chess programming needs, like GUI, Engine integration, 
FEN and PGN parsing.

                 \\|//
                 (o o)
--------------ooO-(_)-Ooo----------------------------------
Copyright (C) 2009  By Gregory A. Prentice Snoqualmie, WA
                    www.cafechess.org
                    cafechess@comcast.net
                      Oooo
--------------oooO----(  )---------------------------------
              (  )    (_/
               \_)

Microsoft Public License (Ms-PL)
               
Revisions:

Author        Date    Description:

*/
using System;
using System.IO;

using Cafechess.Chess.General;
using Cafechess.Chess.Validation;

namespace Cafechess.Chess.Parsers
{
  /// <summary>
  /// Basically it takes the standard 64 squares and presses them into 32 bytes.  
  /// Where we utilize the High and Low halves of the byte for a total of two squares.  
  /// We do use a true 4 bit huffman code for our pieces.  The best would be to use bit
  /// chunks of 2-3 bits for each piece.  But then you would have to do way more bit fiddling
  /// and thus this seems to be the best compromize between size and speed.
  /// <code>
  /// 
  /// </code>
  /// </summary>
  public class HuffmanNotation : IPositionEvents, IPosition
  {
    private const byte Empty  = 0xFF;
    private const byte Enpass = 0x0E;
    private const byte WhiteCK = 0x01;
    private const byte WhiteCQ = 0x02;
    private const byte BlackCK = 0x04;
    private const byte BlackCQ = 0x08;
    private const byte Color   = 0x10;
    private const byte SupEn = 0x01;
    private const byte SupHa = 0x02;
    private const byte SupFu = 0x04;
    private ushort  coHalfMove;
    private ushort  coFullMove;

    #region Events
    public delegate void EventPlacePiece(Pieces piece,int square);
    public event EventPlacePiece EventPlacePieceHandler;

    /// Defines the color hooks that allow call back to set who's move it is.
    public delegate void EventSideToMove(bool bColor);
    public event EventSideToMove EventSideToMoveHandler;

    public delegate void EventCastling(bool WK, bool WQ, bool BK, bool BQ);
    public event EventCastling EventCastlingHandler;

    public delegate void EventEnpassant( string Enpassant );
    public event EventEnpassant EventEnpassantHandler;
    public delegate void EventHalfMoves(int number);
    public event EventHalfMoves EventHalfMovesHandler;
    public delegate void EventFullMoves(int number);
    public event EventFullMoves EventFullMovesHandler;

    public delegate void EventFinished();
    public event EventFinished EventFinishedHandler;
    public delegate void EventStarting();
    public event EventStarting EventStartingHandler;
    
    #endregion Events

    /// <summary>
    /// Only need 4 columns as we use the high/low half of the byte.
    /// We only need 32 bytes to represent the board.
    /// </summary>
    byte [,] coPosition;
    /// <summary>
    /// Byte to hold our castling information and side to move bits.
    /// </summary>
    byte coCastleInfo;
    /// <summary>
    /// Byte to hold what to suppress in output stream.
    /// </summary>
    byte coSuppress;

    /// <summary>
    /// Number of half moves to determine the 50 move rule.
    /// </summary>
    public int HalfMove
    { 
      get{return (int)coHalfMove;}
      set
      {
        if( SuppressHalfMove == false )
          coHalfMove=(ushort)value;
      }
    }
    /// <summary>
    /// Number of completed move cycles, i.e. after black moves.
    /// </summary>
    public int FullMove
    { 
      get{return (int)coFullMove;}
      set
      {
        if( SuppressFullMove == false )
          coFullMove=(ushort)value;
      }
    }
    /// <summary>
    /// If true then white can still castle king side.
    /// </summary>
    public bool WhiteCastleKing
    {
      get{return (coCastleInfo & WhiteCK) > 0;}
      set{ coCastleInfo = (byte)((coCastleInfo & ~WhiteCK) | (value ? WhiteCK : (byte)0x00));}
    }
    /// <summary>
    /// If true then white can still castle queen side.
    /// </summary>    
    public bool WhiteCastleQueen
    {
      get { return (coCastleInfo & WhiteCQ) > 0; }
      set{ coCastleInfo = (byte)((coCastleInfo & ~WhiteCQ) | (value ? WhiteCQ : (byte)0x00));}
    }
    /// <summary>
    /// If true then black can still castle king side.
    /// </summary>
    public bool BlackCastleKing
    {
      get { return (coCastleInfo & BlackCK) > 0; }
      set{ coCastleInfo = (byte)((coCastleInfo & ~BlackCK) | (value ? BlackCK : (byte)0x00));}
    }
    /// <summary>
    /// If true then black can still castle queen side.
    /// </summary>
    public bool BlackCastleQueen
    {
      get { return (coCastleInfo & BlackCQ) > 0; }
      set{ coCastleInfo = (byte)((coCastleInfo & ~BlackCQ) | (value ? BlackCQ : (byte)0x00));}
    }
    /// <summary>
    /// Returns the square number that an enpassant move may be possible.
    /// </summary>
    public int Enpassant
    {
      get
      { 
        byte enpassant;
        int square=-1;
        for(int row = 0; row < 8; row++)
        {
          for(int col = 0; col < 8; col++)
          {            
            // right 4 bits;
            enpassant = this[(row*8)+col];
            if(enpassant == Enpass)
            {
              square = (row*8)+col;
              row = 8;
              break;
            }
          }
        }
        return (int) square;      
      }
      set
      {
        if( SuppressEnpasant == false )
          this[value] = Enpass;
      }
    }
    /// <summary>
    /// True if it is white to move else false for black.
    /// </summary>
    public bool SideToMove
    {
      get{return (coCastleInfo & Color) > 0;}
      set{ coCastleInfo = (byte)((coCastleInfo & ~Color) | (value ? Color : (byte)0x00));}
    }

    public bool SuppressEnpasant
    {
      get{return (coSuppress & SupEn) > 0;}
      set{ coSuppress = (byte)((coSuppress & ~SupEn) | (value ? SupEn : (byte)0x00));}
    }
    public bool SuppressHalfMove
    {
      get{return (coSuppress & SupHa) > 0;}
      set{ coSuppress = (byte)((coSuppress & ~SupHa) | (value ? SupHa : (byte)0x00));}
    }
    public bool SuppressFullMove
    {
      get{return (coSuppress & SupFu) > 0;}
      set{ coSuppress = (byte)((coSuppress & ~SupFu) | (value ? SupFu : (byte)0x00));}
    }

    /// <summary>
    /// Constructor to allocate our internal buffer an initialize our state.
    /// </summary>
    public HuffmanNotation()
    {
      coPosition = new byte[8,4];
      coSuppress = 0;
      Clear();

    }
    /// <summary>
    /// Simply sets all squares to empty, set white to move, castling is set for all sides,
    /// half and full moves set to 0.  Same cleared state as the FenNotation class.
    /// </summary>
    public void Clear()
    {
      for(int row = 0; row < 8; row++)
      {
        for(int col = 0; col < 4; col++)
          coPosition[row,col] = Empty;
      }
      this.SideToMove = true;
      this.WhiteCastleKing = true;
      this.WhiteCastleQueen = true;
      this.BlackCastleKing = true;
      this.BlackCastleQueen = true;
      
      coFullMove = 0;
      coHalfMove = 0;
    }
    /// <summary>
    /// Indexer into the internal buffers for getting and setting squares with pieces.
    /// </summary>
    public byte this [int i]
    {
      get
      {
        int row = i / 8;
        int col = (i % 8)/2;
        byte piece = 0;
        if( i % 2 == 1 )
        {
          // right 4 bits
          piece  = (byte)(coPosition[row,col] & 0x0F);
        }
        else
        {
          // left 4 bits
          piece  = (byte)(coPosition[row,col] >> 4);
        }
        return piece;
      }
      set
      {
        int row = i / 8;
        int col = (i % 8)/2;
        if( i % 2 == 1 )
        {
          // Move into Low(right) 4 bits
          coPosition[row,col] = (byte)((coPosition[row,col] & 0xF0) | value);
        }
        else
        {
          // Move into High(left) 4 bits
          coPosition[row,col] = (byte)((coPosition[row,col] & 0x0F) | (value << 4));
        }
      }
    }
    /// <summary>
    /// Gets and sets our raw huffman encoded position.
    /// </summary>
    public Stream Blob
    {
      get
      { 
        MemoryStream ms = new MemoryStream(37);
        for(int row = 0; row < 8; row++)
        {
          for(int col = 0; col < 4; col++)
            ms.WriteByte(coPosition[row,col]);
        }
        ms.WriteByte(coCastleInfo);
        ms.WriteByte((byte)(coFullMove>>8));
        ms.WriteByte((byte)(coFullMove & 0x00FF));
        ms.WriteByte((byte)(coHalfMove>>8));
        ms.WriteByte((byte)(coHalfMove & 0x00FF));
        ms.Position = 0;
        return ms;
      }
      set
      { 
        Stream ms = value;
        byte twopieces = 0;
        Pieces piece;
        int square=0;
        ms.Position = 0;
        if(EventStartingHandler != null )
          EventStartingHandler();
        for(int row = 0; row < 8; row++)
        {
          for(int col = 0; col < 4; col++)
          {
            twopieces = (byte) ms.ReadByte();
            coPosition[row,col] = twopieces;//ms.ReadByte();
            piece = (Pieces)(twopieces >> 4);
            // Hack with the Delete and ClosedHand as I'm trying to use some existing enums for pieces.
            if( EventPlacePieceHandler != null && piece != Pieces.Delete && piece != Pieces.ClosedHand )
              EventPlacePieceHandler(piece,square);
            square++;
            piece = (Pieces)(twopieces & 0x0F);
            // Hack with the Delete and ClosedHand as I'm trying to use some existing enums for pieces.
            if( EventPlacePieceHandler != null && piece != Pieces.Delete && piece != Pieces.ClosedHand )
              EventPlacePieceHandler(piece,square);
            square++;
          }
        }
        coCastleInfo = (byte) ms.ReadByte();
        if (EventCastlingHandler != null)
        {
            EventCastlingHandler(this.WhiteCastleKing, this.WhiteCastleQueen,
                                 this.BlackCastleKing, this.BlackCastleQueen);
        }
        if (EventSideToMoveHandler != null)
        {
            EventSideToMoveHandler(this.SideToMove);
        }
        if(EventEnpassantHandler != null)
        { 
          if( (int)this.Enpassant > 0 )
            EventEnpassantHandler(BitboardSquare.SquareToAlpha((int)this.Enpassant));
        }
        ushort tmp = (ushort)ms.ReadByte();
        tmp = (ushort)(tmp << 8);
        coFullMove = (ushort)(tmp | ms.ReadByte());
        tmp = (ushort) ms.ReadByte();
        tmp = (ushort)(tmp << 8);
        coHalfMove = (ushort)(tmp | ms.ReadByte());       
        if(EventHalfMovesHandler != null )
          EventHalfMovesHandler((int)coHalfMove);
        if(EventFullMovesHandler != null )
          EventFullMovesHandler((int)coFullMove);
        if(EventFinishedHandler != null )
          EventFinishedHandler();
        
      }
    }
 
    #region IPositionEvents Members

    public void PlacePiece(Pieces piece, int square)
    {
      this[square] = (byte) piece; //PieceToHuffman(piece);

    }

    public void Finished()
    {
      
    }

    public void SetFullMoves(int number)
    {
      this.FullMove = number;
    }

    public void SetSideToMove(bool bColor)
    {
      this.SideToMove = bColor;
    }

    public void SetHalfMoves(int number)
    {
      this.HalfMove = number;
    }

    public void Starting()
    {
      this.Clear();
    }

    public void SetCastling(bool WK, bool WQ, bool BK, bool BQ)
    {
      this.WhiteCastleKing = WK;
      this.WhiteCastleQueen = WQ;
      this.BlackCastleKing = BK;
      this.BlackCastleQueen = BQ;
    }

    public void SetEnpassant(string Enpassant)
    {
      this.Enpassant = BitboardSquare.AlphaToSquare(Enpassant);
    }

    #endregion

    #region IPosition Members
    /// <summary>
    /// This routine is never implemented here, it just satifies the
    /// IPosition member requirements.
    /// </summary>
    /// <param name="str"></param>
    public void Parse(string str)
    {
      throw new Exception("Not implemented for HuffmanPosition");
    }
    /// <summary>
    /// Performs the same function as the HuffmanNotation.Blob setter
    /// propertie.
    /// </summary>
    /// <param name="ioStream"></param>
    void Cafechess.Chess.Parsers.IPosition.Parse(Stream ioStream)
    {
      this.Blob = ioStream;
    }
    /// <summary>
    /// Add the listener to our parsing events.
    /// </summary>
    /// <param name="ievents"></param>
    public void AddEvents(IPositionEvents ievents)
    {
      EventPlacePieceHandler += new EventPlacePiece(ievents.PlacePiece);
      EventSideToMoveHandler += new EventSideToMove(ievents.SetSideToMove);
      EventCastlingHandler += new EventCastling(ievents.SetCastling);
      EventEnpassantHandler += new EventEnpassant(ievents.SetEnpassant);
      EventHalfMovesHandler += new EventHalfMoves(ievents.SetHalfMoves);
      EventFullMovesHandler += new EventFullMoves(ievents.SetFullMoves);
      EventFinishedHandler += new EventFinished(ievents.Finished);
      EventStartingHandler += new EventStarting(ievents.Starting);
    }
    /// <summary>
    /// Remove the listerner of our parsing events.
    /// </summary>
    /// <param name="ievents"></param>
    public void RemoveEvents(IPositionEvents ievents)
    {
      EventPlacePieceHandler -= new EventPlacePiece(ievents.PlacePiece);
      EventSideToMoveHandler -= new EventSideToMove(ievents.SetSideToMove);
      EventCastlingHandler -= new EventCastling(ievents.SetCastling);
      EventEnpassantHandler -= new EventEnpassant(ievents.SetEnpassant);
      EventHalfMovesHandler -= new EventHalfMoves(ievents.SetHalfMoves);
      EventFullMovesHandler -= new EventFullMoves(ievents.SetFullMoves);
      EventFinishedHandler -= new EventFinished(ievents.Finished);
      EventStartingHandler -= new EventStarting(ievents.Starting);
    }

    #endregion
  }

}
